/////////////////////////////////////////////////////////////////////////////
// 这个文件是 JSI 项目的一部分
//
// Copyright (c) 2006 - 2007 (www.xidea.org)
//
// 要查看完整的版权信息和许可信息，请查看源代码中附带的 LICENSE.txt 文件，
// 或者访问 http://xidea.org/ 获得详细信息。
/////////////////////////////////////////////////////////////////////////////

/**
 * 全局日志对象定义及$JSI对日志的设定等
 * requires: boot-browser.js
 *
 * @package core
 * @copyright (c) 2006 - 2007 (www.xidea.org)
 * @author: dawei jin(jindw@xidea.org)
 * @author: DSONet (dsonet@msn.com)
 * @version $Id: log.js 22 2007-12-26 00:45:56Z dsonet $
 */
/**
 * 全局日志对象
 * @instanceof Log 
 * @public 
 */
var $log = function()
{
	/** 
	 * 设置默认日志等级
	 * @friend
	 * @param {string|int} level 日志等级的字符串或数字表示
	 * @return {number} 被设定的日志等级
	 */
	$JSI.setDefaultLogLevel = function(level)
	{
		return ($log.level = defaultLevel = toLevel(level));
	}

	/** 
	 * 设置指定路径模式的日志级别,一般在配置文件（config&#46;js）中调用该方法做类库的全局日志设置. 
	 * 装载单元初始化时，需要创建单元内的日志对象，这时需要寻找匹配的路径，以精确或者最长的模式为准。 
	 * 在脚本装载后，日志即可初始化，全局设置的改变不对已经初始化的日志对象作用。 
	 * @friend
	 * @param {string} path 脚本路径 如org/xidea/jsdoc/&#42;&#42; 通配符与ant文件匹配模式类似（*一级目录匹配，**任意级目录匹配，包括零级） 
	 * @param {string|int} [level=2] 可选值：TRACE : 0,DEBUG : 1,INFO : 2,WARN : 3,ERROR : 4,FATAL : 5,NONE:6
	 */
	$JSI.setLogLevel = function(path, level)
	{
		level = toLevel(level);
		if (path.indexOf("*") < 0)
		{
			exactLevels[path] = level;
		}
		else
		{
			patternLevels.push(
			{
				length: path.length
				, level: level
				, pattern:new RegExp("^" + path.replace(/([\.\$\?\+\(?:\)\[\]\{\}])/g, "\\$1").replace(/\*\*+/g, ".{0,}").replace(/\*/g, "[^/]*") + "$", "")
			}
			);
			patternLevels.sort(function(o1, o2)
			{
				return o1.length - o2.length;
			}
			); //add to pattern
		}
	}
	/**
	 * 生成指定路径的(path)的日志对象,只在ScriptLoader构造时调用。 
	 * @private
	 * @param {string} path 脚本路径
	 * @return {Log}
	 */
	$JSI.buildLog = function(path)
	{
		return new Log(path, getLevel(path));
	}
	/*
	 * 全局日志配置信息 
	 */
	var defaultLevel = 2
		, consoleLevel = 0
		, exactLevels = {}
		, patternLevels = []
		, strLevels = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"]//日志等级
		, formats = //格式化配置
		{
			"plain" : 
			{
				"et" : "{debug} [{time}] '{path}'"//enter
				, "ls" : "\n"// list separator 
				, "os" : "{" // object start
				, "oi" : "\t"// object indent
				, "oe" : "}"//object end
			}
		};
	/*
	 * 获取日志等级的数字表示
	 * @protected
	 * @implicit
	 * @param {string|number}level 等级标识
	 * @return {number}
	 */
	function toLevel(level)
	{
		if (typeof level == "string")
		{
			level = level.toUpperCase();

			for (var i = strLevels.length - 1; i >= 0; i--)
			{
				if (strLevels[i] == level)
					return i;
			}
		}
		else if (level >= 0 && level <= 4)
		{
			return parseInt(level);
		}
		return defaultLevel;
	}
	/*
	 * 获取指定路径的日志等级
	 * @param {string} path 脚本路径 如org/xidea/jsdoc/&#42;&#42; 通配符与ant文件匹配模式类似（*一级目录匹配，**任意级目录匹配，包括零级）
	 * @private
	 * @return {number} 日志等级
	 */
	function getLevel(path)
	{
		var level = exactLevels[path];
		if (level != null)
		{
			return level;
		}
		else
		{
			for (var i = patternLevels.length - 1; i >= 0; i--)
			{
				if (patternLevels[i].pattern.test(path))
				{
					return patternLevels[i].level;
				}
			}
		}
		return defaultLevel;
	};
	/**
	 * 日志系统，JSI有一个全局日志$log,每个加载单元有一个同名的私有日志。 
	 * 私有日志构造时根据加载脚本的路径获取系统配置的输出等级。 
	 * TRACE : 0,DEBUG : 1,INFO : 2,WARN : 3,ERROR : 4,FATAL : 5,NONE:6 
	 * TODO: 通过定义加载拦截器物配置log过滤器 
	 * @public 
	 * @id Log 
	 * @constructor 
	 * @param {string} path 加载单元（脚本文件）路径 ，如：org/xidea/jsdoc/jsdoc.js 
	 * @param {string|int} level 输出等级 
	 */
	function Log(path, level)
	{
		/**
		 * 装载单元（托管脚本）相对路径 
		 * @private 
		 * @readonly 
		 * @typeof string
		 * @member Log
		 */
		this.path = path;

		this.setLevel(level);
	}

	var $p = Log.prototype;
	/** 
	 * 设置日志级别 
	 * @protected
	 * @param {string|int} level 日志级别，可选值：TRACE : 0,DEBUG : 1,INFO : 2,WARN : 3,ERROR : 4,FATAL : 5,NONE:6
	 * @id Log.prototype.setLevel
	 */
	$p.setLevel = function(level)
	{
		this.level =  toLevel(level);
	}
	/** 
	 * 输出跟踪级别的日志信息
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.trace
	 */
	/** 
	 * 输出调试级别的日志信息 
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.debug 
	 */
	/** 
	 * 输出信息级别的日志信息 
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.info
	 */
	/** 
	 * 输出警告级别的日志信息 
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.warn
	 */
	/** 
	 * 输出错误级别的日志信息 
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.error
	 */
	/** 
	 * 输出致命错误级别的日志信息 
	 * @public
	 * @param {mixed} arguments 要输出的信息
	 * @id Log.prototype.fatal
	 */
	/**
	 * 日志输出级别 
	 * @private 
	 * @readonly 
	 * @typeof number
	 * @id Log.prototype.level
	 */
	for (var i = strLevels.length - 2; i >= 0; i--)
	{
		eval("$p." + strLevels[i].toLowerCase() + " = function(){if(this.level <= " + i + "){this.output(arguments, " + i + ");}}");
	}

	/**
	 * 将日志信息输出至控制台（如指定在页面中的Log输出区域） 
	 * @private
	 * @param {string} msgs 要输出的信息
	 * @param {int} level 输出级别
	 * @id Log.prototype.output
	 */
	$p.output = function (msgs, level)
	{
		if (level >= consoleLevel)
		{
			if (!confirm((formatLog(this, msgs, level, "plain"))+ "\n\n继续显示日志 [" + strLevels[level] + "]?"))
			{
				consoleLevel = level + 1;
			}
		}
	}

	/*
	 * 日志信息格式化方法
	 * @param {Log} log Log对象
	 * @param {string} msgs 要输出的信息
	 * @param {int} level 输出级别
	 * @param {string} ft 输出格式
	 * @private
	 * @return {string} 格式化后的信息
	 */
	function formatLog(log, msgs, level, ft)
	{
		var d = new Date
			, ft = formats[ft]
			, o
			, n
			, tmp = [ft.et.replace("{debug}", strLevels[level]).replace("{time}", d.toLocaleString() + ":" + d.getMilliseconds()).replace("{path}", log.path)]
			, i = 0;
		for (; i < msgs.length; i++)
		{
			o = msgs[i];
			if (typeof o == "object" && o != null)
			{
				o instanceof Array ? 0 : tmp.push(o + ft.os);
				for (n in o)
				{
					tmp.push(ft.oi + n + " : " + o[n]);
				}
				tmp.push(ft.oe);
			}
			else
			{
				tmp.push(o);
			}
		}
		return tmp.join(ft.ls);
	}

	/** 
	 * 设置日志的输出目标。 
	 * 
	 * @param {function|HTMLElement} console 控制台对象，可以是一个接受3个参数（log instance, message list ,message level）的日志输出函数、一个div或其他元素
	 * @friend
	 */
	$JSI.setConsole = function(console)
	{
		//for function 
		if (typeof console == "function")
		{
			$p.output = function(msgs, level)
			{
				console(this, msgs, level);
			}
		}
		else 
		{
			switch (console.tagName)
			{
				case "INPUT":
				case "TEXTAREA":
					$p.output = function(msgs, level)
					{
						console.value += formatLog(this, msgs, level, "plain");
					}
					break;
				case "IFRAME":
				case "FRAME":
				case null:
					break;
				default:
					if (console.appendChild)
					{
						var doc = console.ownerDocument || document
							, defaultColors = ["#666666", "#008000", "#000099",	"#999999", "#FF0000", "#660066"];//html格式化颜色
						formats.html =
						{
							"et" : "<b>{debug}</b><i>{time}</i><q>{path}</q>"
							, "ls" : "<br />"
							, "os" : "<code>"
							, "oi" : "&nbsp;&nbsp;"
							, "oe" : "</code>"
						};
						/*
						 * 默认的日志信息html格式化方法
						 * @param {string} msgs 要输出的信息
						 * @param {int} level 输出级别
						 * @private 
						 */
						$p.output = function(msgs, level)
						{
							var div = console.appendChild(doc.createElement("DIV"));
							div.style.color = defaultColors[level];
							div.className = "jsi-log-" + strLevels[level].toLowerCase();
							div.innerHTML = formatLog(this, msgs, level, "html");
							div = null;
						}
					}
			}
		}
	}

	return new Log("core", defaultLevel);
}();